## 6.12. Простой интерпретатор команд.
Данный раздел просто приводит исходный текст простого интерпретатора команд. Функция match описана в главе "Текстовая обработка".

    /* Примитивный интерпретатор команд. Распознает построчно
     * команды вида: CMD ARG1 ... ARGn <FILE >FILE >>FILE >&FILE >>&FILE
     * Сборка: cc -U42 -DCWDONLY sh.c match.c pwd.c -o sh
     */

    #include <sys/types.h>/* определение типов, используемых системой */
    #include <stdio.h>      /* описание библиотеки ввода/вывода  */
    #include <signal.h>     /* описание сигналов                 */
    #include <fcntl.h>      /* определение O_RDONLY              */
    #include <errno.h>      /* коды системных ошибок             */
    #include <ctype.h>      /* макросы для работы с символами    */
    #include <dirent.h>     /* эмуляция файловой системы BSD 4.2 */
    #include <pwd.h>        /* работа с /etc/passwd              */
    #include <sys/wait.h>   /* описание формата wait()           */

    char cmd[256];          /* буфер для считывания команды */
    #define   MAXARGS 256   /* макс. количество аргументов  */
    char *arg[MAXARGS];     /* аргументы команды */
    char *fin, *fout;       /* имена для перенаправления ввода/вывода */
    int  rout;              /* флаги перенаправления вывода */

    char *firstfound;       /* имя найденной, но невыполняемой программы */
    #define LIM ':'         /* разделитель имен каталогов в path */
    extern char *malloc(), *getenv(), *strcpy(), *getwd();
    extern char *strchr(), *execat();
    extern void callshell(), printenv(), setenv(), dowait(), setcwd();
    extern struct passwd   *getpwuid();
            /* Предопределенные переменные */
    extern char **environ;  /* окружение: изначально смотрит на тот же
                             * массив, что и ev из main() */
    extern int errno;       /* код ошибки системного вызова   */

    char *strdup(s)char *s;
    { char *p; return(p=malloc(strlen(s)+1), strcpy(p,s)); }
            /* strcpy() возвращает свой первый аргумент */
    char *str3spl(s, p, q) char *s, *p, *q;
    { char *n = malloc(strlen(s)+strlen(p)+strlen(q)+1);
      strcpy(n, s); strcat(n, p); strcat(n, q); return n;
    }

    int cmps(s1, s2) char **s1, **s2;
    { return strcmp(*s1, *s2); }
    /* Перенаправить вывод */
    #define APPEND 0x01
    #define ERRTOO 0x02
    int output (name, append, err_too, created) char *name; int *created;
    {
        int     fd;
        *created = 0;     /* Создан ли файл ? */

        if( append ){                   /* >>file */
            /* Файл name существует? Пробуем открыть на запись */
            if((fd = open (name, O_WRONLY)) < 0) {
                if (errno == ENOENT) /* Файл еще не существовал */
                    goto CREATE;
                else
                    return 0;    /* Не имеем права писать в этот файл */
            }
            /* иначе fd == открытый файл, *created == 0 */
        }else{
    CREATE: /* Пытаемся создать (либо опустошить) файл "name" */
            if((fd = creat (name, 0666)) < 0 )
                        return 0;     /* Не могу создать файл  */
            else        *created = 1; /* Был создан новый файл */
        }
        if (append)
            lseek (fd, 0l, 2);      /* на конец файла */
     /* перенаправить стандартный вывод */
        dup2(fd, 1);
        if( err_too ) dup2(fd, 2);  /* err_too=1 для >& */
        close(fd); return 1;
    }

    /* Перенаправить ввод */
    int input (name) char *name;
    {
        int     fd;
        if((fd = open (name, O_RDONLY)) < 0 ) return 0;/* Не могу читать */
     /* перенаправить стандартный ввод */
        dup2(fd, 0); close(fd); return 1;
    }
    /* запуск команды */
    int cmdExec(progr, av, envp, inp, outp, outflg)
            char *progr;       /* имя программы */
            char **av;         /* список аргументов */
            char **envp;       /* окружение */
            char *inp, *outp;  /* файлы ввода-вывода (перенаправления) */
            int outflg;        /* режимы перенаправления вывода */
    {
            void (*del)(), (*quit)();
            int pid;
            int cr = 0;

            del = signal(SIGINT, SIG_IGN); quit = signal(SIGQUIT, SIG_IGN);
            if( ! (pid = fork())){     /* ветвление */
               /* порожденный процесс (сын) */
               signal(SIGINT, SIG_DFL); /* восстановить реакции */
               signal(SIGQUIT,SIG_DFL); /* по умолчанию         */
               /* getpid() выдает номер (идентификатор) данного процесса */
               printf( "Процесс pid=%d запущен\n", pid = getpid());

               /* Перенаправить ввод-вывод */
               if( inp ) if(!input( inp )){
                 fprintf(stderr, "Не могу <%s\n", inp ); goto Err;
               }
               if( outp )
                   if(!output (outp, outflg & APPEND, outflg & ERRTOO, &cr)){
                       fprintf(stderr, "Не могу >%s\n", outp ); goto Err;
                   }
     /* Заменить программу: при успехе
      * данная программа завершается, а вместо нее вызывается
      * функция main(ac, av, envp) программы, хранящейся в файле progr.
      * ac вычисляет система.
      */
               execvpe(progr, av, envp);

    Err:
      /* при неудаче печатаем причину и завершаем порожденный процесс */
               perror(firstfound ? firstfound: progr);
               /* Мы не делаем free(firstfound),firstfound = NULL
                * потому что данный процесс завершается (и тем ВСЯ его
                * память освобождается) :
                */
               if( cr && outp )  /* был создан новый файл     */
                   unlink(outp); /* но теперь он нам не нужен */

               exit(errno);
            }
            /* процесс - отец */

            /* Сейчас сигналы игнорируются, wait не может быть оборван
             * прерыванием с клавиатуры */
            dowait();       /* ожидать окончания сына */
            /* восстановить реакции на сигналы от клавиатуры */
            signal(SIGINT, del); signal(SIGQUIT, quit);
            return pid;     /* вернуть идентификатор сына */
    }
    /* Запуск программы с поиском по переменной среды PATH */
    int execvpe(progr, av, envp) char *progr, **av, **envp;
    {
            char *path, *cp;
            int try = 1;
            register eacces = 0;
            char fullpath[256];     /* полное имя программы */

            firstfound = NULL;
            if((path = getenv("PATH")) == NULL )
                path = ".:/bin:/usr/bin:/etc";
            /* имя: короткое или путь уже задан ? */
            cp = strchr(progr, '/') ? "" : path;
            do{     /* пробуем разные варианты */
                    cp = execat(cp, progr, fullpath);
            retry:
                fprintf(stderr, "пробуем \"%s\"\n", fullpath );
                    execve(fullpath, av, envp);
                    /* если программа запустилась, то на этом месте данный
                     * процесс заменился новой программой. Иначе - ошибка. */
                    switch( errno ){  /* какова причина неудачи ? */
                    case ENOEXEC:  /* это командный файл */
                            callshell(fullpath, av, envp);
                            return (-1);
                    case ETXTBSY:   /* файл записывается */
                            if( ++try > 5 ) return (-1);
                            sleep(try); goto retry;
                    case EACCES:    /* не имеете права */
                            if(firstfound == NULL)
                               firstfound = strdup(fullpath);
                            eacces++; break;
                    case ENOMEM:    /* программа не лезет в память */
                    case E2BIG:
                            return (-1);
                    }
            }while( cp );
            if( eacces ) errno = EACCES;
            return (-1);
    }

    /* Склейка очередной компоненты path и имени программы name */
    static char *execat(path, name, buf)
            register char *path, *name;
            char *buf;      /* где будет результат */
    {
            register char *s = buf;
            while(*path && *path != LIM )
                    *s++ = *path++;         /* имя каталога */
            if( s != buf ) *s++ = '/';
            while( *name )
                    *s++ = *name++;         /* имя программы */
            *s = '\0';
            return ( *path ? ++path /* пропустив LIM */ : NULL );
    }

    /* Запуск командного файла при помощи вызова интерпретатора */
    void callshell(progr, av, envp) char *progr, **av, **envp;
    {
            register i; char *sh; char *newav[MAXARGS+2];
            int fd; char first = 0;

            if((fd = open(progr, O_RDONLY)) < 0 )
                    sh = "/bin/sh";
            else{
                    read(fd, &first, 1); close(fd);
                    sh = (first == '#') ? "/bin/csh" : "/bin/sh";
            }
            newav[0] = "Shellscript"; newav[1] = progr;
            for(i=1; av[i]; i++)
                    newav[i+1] = av[i];
            newav[i+1] = NULL;
            printf( "Вызываем %s\n", sh );
            execve(sh, newav, envp);
    }

    /* Ожидать окончания всех процессов, выдать причины смерти. */
    void dowait(){
            int ws; int pid;

            while((pid = wait( &ws)) > 0 ){
              if( WIFEXITED(ws)){
                printf( "Процесс %d умер с кодом %d\n",
                                 pid,            WEXITSTATUS(ws));
              }else if( WIFSIGNALED(ws)){
                printf( "Процесс %d убит сигналом %d\n",
                                 pid,             WTERMSIG(ws));
                if(WCOREDUMP(ws)) printf( "Образовался core\n" );
                /* core - образ памяти процесса для отладчика adb */
              }else if( WIFSTOPPED(ws)){
                printf( "Процесс %d остановлен сигналом %d\n",
                                 pid,            WSTOPSIG(ws));
              }
           }
    }
    /* Расширение шаблонов имен. Это упрощенная версия, которая
     * расширяет имена только в текущем каталоге.
     */
    void glob(dir, args, indx, str /* что расширять */, quote )
         char *args[], *dir; int *indx; char *str;
         char quote; /* кавычки, в которые заключена строка str */
    {
            static char globchars[] = "*?[";
            char *p; char **start = &args[ *indx ];
            short nglobbed = 0;

            register struct dirent *dirbuf;
            DIR *fd; extern DIR *opendir();

            /* Затычка для отмены глоббинга: */
            if( *str == '\\' ){ str++;    goto noGlob; }

            /* Обработка переменных $NAME    */
            if( *str == '$' && quote != '\'' ){
                    char *s = getenv(str+1);
                    if( s ) str = s;
            }
            /* Анализ: требуется ли глоббинг */
            if( quote ) goto noGlob;
            for( p=str; *p; p++ )  /* Есть ли символы шаблона? */
                    if( strchr(globchars, *p))
                            goto doGlobbing;
    noGlob:
            args[ (*indx)++ ] = strdup(str);
            return;

    doGlobbing:
           if((fd = opendir (dir)) == NULL){
                  fprintf(stderr, "Can't read %s\n", dir); return;
           }
           while ((dirbuf = readdir (fd)) != NULL ) {
                  if (dirbuf->d_ino == 0) continue;
                  if (strcmp (dirbuf->d_name, ".") == 0 ||
                      strcmp (dirbuf->d_name, "..") == 0) continue;
                  if( match( dirbuf->d_name, str)){
                      args[ (*indx)++ ] = strdup(dirbuf->d_name);
                      nglobbed++;
                  }
           }
           closedir(fd);
           if( !nglobbed){
               printf( "%s: no match\n", str);
               goto noGlob;
           }else{   /* отсортировать */
               qsort(start, nglobbed, sizeof (char *), cmps);
           }
    }

    /* Разбор командной строки */
    int parse(s) register char *s;
    {
            int i; register char *p;
            char tmp[80];   /* очередной аргумент */
            char c;

            /* очистка старых аргументов */
            for(i=0; arg[i]; i++) free(arg[i]), arg[i] = NULL;
            if( fin  ) free(fin ), fin  = NULL;
            if( fout ) free(fout), fout = NULL;
            rout = 0;

            /* разбор строки */
            for( i=0 ;; ){
                    char quote = '\0';

                    /* пропуск пробелов - разделителей слов */
                    while((c = *s) && isspace(c)) s++;
                    if( !c ) break;
                    /* очередное слово */
                    p = tmp;
                    if(*s == '\'' || *s == '"' ){
                    /* аргумент в кавычках */
                            quote = *s++;  /* символ кавычки */
                            while((c = *s) != '\0' && c != quote){
                               if( c == '\\' ){ /* заэкранировано */
                                   c = *++s;
                                   if( !c ) break;
                               }
                               *p++ = c; ++s;
                            }
                            if(c == '\0')
                      fprintf(stderr, "Нет закрывающей кавычки %c\n", quote);
                            else s++; /* проигнорировать кавычку на конце */
                    } else
                            while((c = *s) && !isspace(c)){
                               if(c == '\\') /* заэкранировано */
                                    if( !(c = *++s))
                                         break /* while */;
                               *p++ = c; s++;
                            }
                    *p = '\0';
                    /* Проверить, не есть ли это перенаправление
                     * ввода/вывода. В отличие от sh и csh
                     * здесь надо писать >ФАЙЛ <ФАЙЛ
                     * >< вплотную к имени файла.
                     */
                    p = tmp;        /* очередное слово */
                    if( *p == '>'){ /* перенаправлен вывод */
                        p++;
                        if( fout ) free(fout), rout = 0; /* уже было */
                        if( *p == '>' ){ rout |= APPEND; p++; }
                        if( *p == '&' ){ rout |= ERRTOO; p++; }
                        if( !*p ){
                                fprintf(stderr, "Нет имени для >\n");
                                fout = NULL; rout = 0;
                        } else  fout = strdup(p);
                    } else if( *p == '<' ){  /* перенаправлен ввод */
                        p++;
                        if( fin ) free(fin);        /* уже было */
                        if( !*p ){
                                fprintf(stderr, "Нет имени для <\n");
                                fin = NULL;
                        } else  fin = strdup(p);
                    } else /* добавить имена к аргументам */
                                glob( ".", arg, &i, p, quote );
            }
            arg[i] = NULL; return i;
    }

    /* Установить имя пользователя */
    void setuser(){
            int uid = getuid();     /* номер пользователя, запустившего Шелл */
            char *user = "mr. Nobody";      /* имя пользователя     */
            char *home = "/tmp";            /* его домашний каталог */
            struct passwd *pp = getpwuid( uid );
            if( pp != NULL ){
                    if(pp->pw_name && *pp->pw_name ) user = pp->pw_name;
                    if(               *pp->pw_dir  ) home = pp->pw_dir;
            }
            setenv("USER", user); setenv("HOME", home);
    }

    void setcwd(){ /* Установить имя текущего каталога */
            char cwd[512];
            getwd(cwd); setenv( "CWD", cwd );
    }
    void main(ac, av, ev) char *av[], *ev[]; {
            int argc;              /* количество аргументов */
            char *prompt;          /* приглашение           */

            setuser(); setcwd();
            signal(SIGINT, SIG_IGN);
            setbuf(stdout, NULL);  /* отменить буферизацию */
            for(;;){
                 prompt = getenv( "prompt" ); /* setenv prompt -->\  */
                 printf( prompt ? prompt : "@ ");/* приглашение */
                 if( gets(cmd) == NULL /* at EOF  */ ) exit(0);
                 argc = parse(cmd);
                 if( !argc) continue;
                 if( !strcmp(arg[0], "exit" )) exit(0);

                 if( !strcmp(arg[0], "cd" )){
                     char *d = (argc==1) ? getenv("HOME"):arg[1];
                     if(chdir(d) < 0)
                        printf( "Не могу войти в %s\n", d );
                     else setcwd();
                     continue;
                 }

                 if( !strcmp(arg[0], "echo" )){
                     register i; FILE *fp;
                     if( fout ){
                        if((fp = fopen(fout, rout & APPEND ? "a":"w"))
                               == NULL) continue;
                     }  else fp = stdout;
                     for(i=1; i < argc; i++ )
                        fprintf( fp, "%s%s", arg[i], i == argc-1 ? "\n":"   ");
                     if( fp != stdout ) fclose(fp);
                     continue;
                 }

                 if( !strcmp(arg[0], "setenv" )){
                          if( argc == 1 ) printenv();
                     else if( argc == 2 ) setenv( arg[1], "" );
                     else                 setenv( arg[1], arg[2]);
                     continue;
                 }
                 cmdExec(arg[0], (char **) arg, environ, fin, fout, rout);
            }
    }

    /* -----------------------------------------------------------*/
    /* Отсортировать и напечатать окружение */
    void printenv(){
            char *e[40]; register i = 0; char *p, **q = e;

            do{
                 p = e[i] = environ[i]; i++;
            } while( p );

    #ifdef SORT
            qsort( e, --i /* сколько */, sizeof(char *), cmps);
    #endif
            while( *q )
                 printf( "%s\n", *q++ );
    }

    /* Сравнение имени переменной окружения с name */
    static char *envcmp(name, evstr) char *name, *evstr;
    {
            char *p; int code;
            if((p = strchr(evstr, '=')) == NULL ) return NULL; /* error ! */
            *p = '\0';      /* временно */
            code = strcmp(name, evstr);
            *p = '=';       /* восстановили */
            return code==0 ? p+1 : NULL;
    }

    /* Установить переменную окружения */
    void setenv( name, value ) char *name, *value;
    {
            static malloced = 0;    /* 1, если environ перемещен */
            char *s, **p, **newenv;
            int len, change_at = (-1), i;

            /* Есть ли переменная name в environ-е ? */
            for(p = environ; *p; p++ )
                    if(s = envcmp(name, *p)){       /* уже есть */
                            if((len = strlen(s)) >= strlen(value)){
                                /* достаточно места */
                                strcpy(s, value); return;
                            }
                            /* Если это новый environ ... */
                            if( malloced ){
                                free( *p ); *p = str3spl(name, "=", value);
                                return;
                            }
                            /* иначе создаем копию environ-а */
                            change_at = p - environ;        /* индекс */
                            break;
                    }
            /* Создаем копию environ-а. Если change_at == (-1), то
             * резервируем новую ячейку для еще не определенной переменной */
            for(p=environ, len=0; *p; p++, len++ );
            /* вычислили количество переменных */
            if( change_at < 0 ) len++;
            if((newenv = (char **) malloc( sizeof(char *) * (len+1)))
                    == (char **) NULL) return;
            for(i=0; i < len+1; i++ ) newenv[i] = NULL;     /* зачистка */
            /* Копируем старый environ в новый */
            if( !malloced ) /* исходный environ в стеке (дан системой) */
                 for(i=0; environ[i]; i++ ) newenv[i] = strdup(environ[i]);
            else for(i=0; environ[i]; i++ ) newenv[i] =        environ[i];
            /* Во втором случае строки уже были спасены, копируем ссылки */

            /* Изменяем, если надо: */
            if( change_at >= 0 ){
                    free( newenv[change_at] );
                    newenv[change_at] = str3spl(name, "=", value);
            } else {        /* добавить в конец новую переменную */
                    newenv[len-1] = str3spl(name, "=", value);
            }
            /* подменить environ */
            if( malloced ) free( environ );
            environ = newenv; malloced++;
            qsort( environ, len, sizeof(char *), cmps);
    }

    /* Допишите команды:
       unsetenv имя_переменной - удаляет переменную среды;
       exit N                  - завершает интерпретатор с
                                 кодом возврата N (это целое число);
    */
